{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e06c733a-1124-44b5-a634-37d0887fdfe6",
   "metadata": {},
   "source": [
    "# The Price is Right\n",
    "\n",
    "## Week 8 Order of Play\n",
    "\n",
    "Day 1: Modal.com and SpecialistAgent  \n",
    "Day 2: RAG, FrontierAgent, Ensemble Agent  \n",
    "Day 3: ScannerAgent, MessengerAgent  \n",
    "Day 4: AutonomousPlannerAgent and DealAgentFramework  \n",
    "Day 5: The Price Is Right Finale\n",
    "\n",
    "## RAG (Retrieval Augmented Generation) based on a dataset of 800,000 scraped Amazon products\n",
    "\n",
    "#### For our 2nd agent, we will be asking OpenAI to estimate the price of one of our deals - and we will give it a hand.\n",
    "\n",
    "We discovered that LLMs are really good at this, out of the box.\n",
    "\n",
    "And we discovered that we can beat a frontier LLM by fine-tuning an open-source LLM.\n",
    "\n",
    "Now we are going to try **inference time** techniques instead of training -- by using RAG!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2db71ba5-55a8-48b7-97d5-9db8dc872837",
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "\n",
    "import os\n",
    "import logging\n",
    "from dotenv import load_dotenv\n",
    "from huggingface_hub import login\n",
    "import numpy as np\n",
    "import re\n",
    "from sentence_transformers import SentenceTransformer\n",
    "import chromadb\n",
    "from sklearn.manifold import TSNE\n",
    "import plotly.graph_objects as go\n",
    "from litellm import completion\n",
    "from tqdm.notebook import tqdm\n",
    "from agents.evaluator import evaluate\n",
    "from agents.items import Item"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b044d040-e467-4463-a3a5-119939ca8199",
   "metadata": {},
   "outputs": [],
   "source": [
    "# environment\n",
    "\n",
    "load_dotenv(override=True)\n",
    "DB = \"products_vectorstore\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c1cb7f1-41f7-4df8-95fa-f3143b4ce312",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Log in to HuggingFace\n",
    "# If you don't have a HuggingFace account, you can set one up for free at www.huggingface.co\n",
    "# And then add the HF_TOKEN to your .env file as explained in the project README\n",
    "\n",
    "hf_token = os.environ['HF_TOKEN']\n",
    "login(token=hf_token, add_to_git_credential=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15ec0e43",
   "metadata": {},
   "outputs": [],
   "source": [
    "LITE_MODE = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5b8f790",
   "metadata": {},
   "outputs": [],
   "source": [
    "username = \"ed-donner\"\n",
    "dataset = f\"{username}/items_lite\" if LITE_MODE else f\"{username}/items_full\"\n",
    "\n",
    "train, val, test = Item.from_hub(dataset)\n",
    "\n",
    "print(f\"Loaded {len(train):,} training items, {len(val):,} validation items, {len(test):,} test items\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf43f181-8b51-43c8-9763-599220cf6e66",
   "metadata": {},
   "source": [
    "# Now create a Chroma Datastore\n",
    "\n",
    "Now we will use the free, open-source Vector database Chroma.  \n",
    "We will create a Chroma datastore with 400,000 products from our training dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6ba77914-ea9a-4b92-9280-863ee07ca8d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "client = chromadb.PersistentClient(path=DB)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1744c683-847a-4151-b6e0-56066f1fe4b0",
   "metadata": {},
   "source": [
    "# Introducing the SentenceTransformer Encoding LLM\n",
    "\n",
    "The all-MiniLM is a very useful model from HuggingFace that maps sentences & paragraphs to 384 dimensional vectors and is ideal for tasks like semantic search.\n",
    "\n",
    "https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2\n",
    "\n",
    "It can run pretty quickly locally.\n",
    "\n",
    "As an alternative, OpenAI provides a closed-source Embeddings model. Benefits compared to OpenAI embeddings:\n",
    "1. It's free and fast!\n",
    "3. We can run it locally, so the data never leaves our box - might be useful if you're building a personal RAG"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af2545a0-e160-41db-8914-f77b1c7eff26",
   "metadata": {},
   "outputs": [],
   "source": [
    "encoder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32abb023-64b5-40a4-bfc1-e22c3ec31221",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pass in a list of texts, get back a numpy array of vectors\n",
    "\n",
    "vector = encoder.encode([\"A proficient AI engineer who has almost reached the finale of AI Engineering Core Track!\"])[0]\n",
    "print(vector.shape)\n",
    "vector"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa837b98-17ff-486e-ac30-a4b4f794af7b",
   "metadata": {},
   "source": [
    "## With that background, let's populate our Chroma database\n",
    "\n",
    "### By calculating vectors for 800,000 scraped products\n",
    "\n",
    "This takes 30 minutes on my machine on my GPU - it might take longer for you - feel free to use the Lite dataset!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1c8f101-9c81-462d-be2e-9b479831857f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Check if the collection exists; if not, create it\n",
    "\n",
    "collection_name = \"products\"\n",
    "existing_collection_names = [collection.name for collection in client.list_collections()]\n",
    "\n",
    "if collection_name not in existing_collection_names:\n",
    "    collection = client.create_collection(collection_name)\n",
    "    for i in tqdm(range(0, len(train), 1000)):\n",
    "        documents = [item.summary for item in train[i: i+1000]]\n",
    "        vectors = encoder.encode(documents).astype(float).tolist()\n",
    "        metadatas = [{\"category\": item.category, \"price\": item.price} for item in train[i: i+1000]]\n",
    "        ids = [f\"doc_{j}\" for j in range(i, i+1000)]\n",
    "        ids = ids[:len(documents)]\n",
    "        collection.add(ids=ids, documents=documents, embeddings=vectors, metadatas=metadatas)\n",
    "\n",
    "collection = client.get_or_create_collection(collection_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c65375e1-a8eb-4203-b8f1-dfff69a693cc",
   "metadata": {},
   "source": [
    "# Let's visualize the vectorized data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "202c3b08-dc89-4995-a25c-041417ec9b9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# It is very fun turning this up to 800_000 and seeing the full dataset visualized,\n",
    "# but it almost crashes my box every time so do that at your own risk!! 10_000 is safe!\n",
    "\n",
    "MAXIMUM_DATAPOINTS = 10_000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c653565a-6405-4c5a-b925-7e14a17bf2da",
   "metadata": {},
   "outputs": [],
   "source": [
    "CATEGORIES = ['Appliances', 'Automotive', 'Cell_Phones_and_Accessories', 'Electronics','Musical_Instruments', 'Office_Products', 'Tools_and_Home_Improvement', 'Toys_and_Games']\n",
    "COLORS = ['cyan', 'blue', 'brown', 'orange', 'yellow', 'green' , 'purple', 'red']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a754334-69ef-4b4f-92c7-d7da89457f7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prework\n",
    "result = collection.get(include=['embeddings', 'documents', 'metadatas'], limit=MAXIMUM_DATAPOINTS)\n",
    "vectors = np.array(result['embeddings'])\n",
    "documents = result['documents']\n",
    "categories = [metadata['category'] for metadata in result['metadatas']]\n",
    "colors = [COLORS[CATEGORIES.index(c)] for c in categories]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a30b5e9-7dd9-45c1-a9a7-74cb22cdef2f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's try a 2D chart\n",
    "# TSNE stands for t-distributed Stochastic Neighbor Embedding - it's a common technique for reducing dimensionality of data\n",
    "\n",
    "tsne = TSNE(n_components=2, random_state=42)\n",
    "reduced_vectors = tsne.fit_transform(vectors)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7a97fc5-9f44-4f1d-a253-8c8f0bcd9ec9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the 2D scatter plot\n",
    "fig = go.Figure(data=[go.Scatter(\n",
    "    x=reduced_vectors[:, 0],\n",
    "    y=reduced_vectors[:, 1],\n",
    "    mode='markers',\n",
    "    marker=dict(size=4, color=colors, opacity=0.7),\n",
    "    text=[f\"Category: {c}<br>Text: {d[:50]}...\" for c, d in zip(categories, documents)],\n",
    "    hoverinfo='text'\n",
    ")])\n",
    "\n",
    "fig.update_layout(\n",
    "    title='2D Chroma Vectorstore Visualization',\n",
    "    scene=dict(xaxis_title='x', yaxis_title='y'),\n",
    "    width=1200,\n",
    "    height=800,\n",
    "    margin=dict(r=20, b=10, l=10, t=40)\n",
    ")\n",
    "\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bdb35e3-bd0d-4569-872b-34bea8316675",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's try 3D!\n",
    "\n",
    "tsne = TSNE(n_components=3, random_state=42)\n",
    "reduced_vectors = tsne.fit_transform(vectors)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4361c151-9f1b-4652-9204-695baf3860d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the 3D scatter plot\n",
    "fig = go.Figure(data=[go.Scatter3d(\n",
    "    x=reduced_vectors[:, 0],\n",
    "    y=reduced_vectors[:, 1],\n",
    "    z=reduced_vectors[:, 2],\n",
    "    mode='markers',\n",
    "    marker=dict(size=2, color=colors, opacity=0.7),\n",
    "    text=[f\"Category: {c}<br>Text: {d[:50]}...\" for c, d in zip(categories, documents)],\n",
    "    hoverinfo='text'\n",
    ")])\n",
    "\n",
    "fig.update_layout(\n",
    "    title='3D Chroma Vector Store Visualization',\n",
    "    scene=dict(xaxis_title='x', yaxis_title='y', zaxis_title='z'),\n",
    "    width=1200,\n",
    "    height=800,\n",
    "    margin=dict(r=20, b=10, l=10, t=40)\n",
    ")\n",
    "\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "caa8e99f",
   "metadata": {},
   "outputs": [],
   "source": [
    "test[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b72f1b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def vector(item):\n",
    "    return encoder.encode(item.summary)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5dc3419",
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_similars(item):\n",
    "    vec = vector(item)\n",
    "    results = collection.query(query_embeddings=vec.astype(float).tolist(), n_results=5)\n",
    "    documents = results['documents'][0][:]\n",
    "    prices = [m['price'] for m in results['metadatas'][0][:]]\n",
    "    return documents, prices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98de579a",
   "metadata": {},
   "outputs": [],
   "source": [
    "find_similars(test[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c74a25b1-f93c-4a75-9999-09e262f9abc9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# We need to give some context to GPT-5.1 by selecting 5 products with similar descriptions\n",
    "\n",
    "def make_context(similars, prices):\n",
    "    message = \"For context, here are some other items that might be similar to the item you need to estimate.\\n\\n\"\n",
    "    for similar, price in zip(similars, prices):\n",
    "        message += f\"Potentially related product:\\n{similar}\\nPrice is ${price:.2f}\\n\\n\"\n",
    "    return message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b10dd77",
   "metadata": {},
   "outputs": [],
   "source": [
    "documents, prices = find_similars(test[0])\n",
    "print(make_context(documents, prices))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05b57490-060d-47ff-9cf0-2b61b455bcd8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def messages_for(item, similars, prices):\n",
    "    message = f\"Estimate the price of this product. Respond with the price, no explanation\\n\\n{item.summary}\\n\\n\"\n",
    "    message += make_context(similars, prices)\n",
    "    return [{\"role\": \"user\", \"content\": message}]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a81ff141",
   "metadata": {},
   "outputs": [],
   "source": [
    "documents, prices = find_similars(test[0])\n",
    "print(messages_for(test[0], documents, prices)[0]['content'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "721ab130-b6d8-4356-9704-687c9bc2636f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The function for gpt-5-mini\n",
    "\n",
    "def gpt_5__1_rag(item):\n",
    "    documents, prices = find_similars(item)\n",
    "    response = completion(model=\"gpt-5.1\", messages=messages_for(item, documents, prices), reasoning_effort=\"none\", seed=42)\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "613a6278-2da5-4bf8-b733-2947736feb63",
   "metadata": {},
   "outputs": [],
   "source": [
    "# How much does our favorite distortion pedal cost?\n",
    "\n",
    "test[0].price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d30586f2-6b84-4750-acf5-a113ac9ccb48",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's do this!!\n",
    "\n",
    "gpt_5__1_rag(test[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "846067a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(gpt_5__1_rag, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cb035c28",
   "metadata": {},
   "outputs": [],
   "source": [
    "import modal\n",
    "Pricer = modal.Cls.from_name(\"pricer-service\", \"Pricer\")\n",
    "pricer = Pricer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "715300c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def specialist(item):\n",
    "    return pricer.price.remote(item.summary)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3acd1a24",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_price(reply):\n",
    "    reply = reply.replace(\"$\", \"\").replace(\",\", \"\")\n",
    "    match = re.search(r\"[-+]?\\d*\\.\\d+|\\d+\", reply)\n",
    "    return float(match.group()) if match else 0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "944a9b15",
   "metadata": {},
   "source": [
    "## Download the Neural Network weights from Week 6 into this directory\n",
    "\n",
    "The file `deep_neural_network.pth` here:\n",
    "\n",
    "https://drive.google.com/drive/folders/1uq5C9edPIZ1973dArZiEO-VE13F7m8MK?usp=drive_link"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b45acd2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "from agents.deep_neural_network import DeepNeuralNetworkInference\n",
    "\n",
    "runner = DeepNeuralNetworkInference()\n",
    "runner.setup()\n",
    "runner.load(\"deep_neural_network.pth\")\n",
    "\n",
    "def deep_neural_network(item):\n",
    "    return runner.inference(item.summary)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6327b3bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def ensemble(item):\n",
    "    price1 = get_price(gpt_5__1_rag(item))\n",
    "    price2 = specialist(item)\n",
    "    price3 = deep_neural_network(item)\n",
    "    return price1 * 0.8 + price2 * 0.1 + price3 * 0.1\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9624bce1",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(ensemble, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2684714-e6d0-47d6-bf31-87fe349fc15c",
   "metadata": {},
   "outputs": [],
   "source": [
    "root = logging.getLogger()\n",
    "root.setLevel(logging.INFO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c293f0a8-7097-4744-a2e9-7da5268406a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from agents.frontier_agent import FrontierAgent\n",
    "\n",
    "agent = FrontierAgent(collection)\n",
    "agent.price(\"Quadcast HyperX condenser mic, connects via usb-c to your computer for crystal clear audio\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8043d5e4-ec42-4fa0-9344-28a856d4f6d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent.price(\"Shure MV7+ professional podcaster microphone with usb-c and XLR outputs\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39799a17-20ad-45ef-8c04-4712f189c9d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from agents.neural_network_agent import NeuralNetworkAgent\n",
    "agent = NeuralNetworkAgent()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "029d5dd2",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent.price(\"Shure MV7+ professional podcaster microphone with usb-c and XLR outputs\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de6ace7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from agents.ensemble_agent import EnsembleAgent\n",
    "agent = EnsembleAgent(collection)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72533425",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent.price(\"Shure MV7+ professional podcaster microphone with usb-c and XLR outputs\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c22c03a4",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
